package com.aldobucchi.utils
{
	import flash.events.TimerEvent;
	import flash.utils.Dictionary;
	import flash.utils.Timer;
	
	import com.aldobucchi.x.flash.utils.Timer.FrameTimer;
	
	
	/**
	 * 
	 * Provides a consistent way to implement the enable/disable auto update pattern.
	 * It could have been named TransactionalFlag or something more generic, but we chose
	 * to keep the naming convention used within the flex framework ( ArrayCollection.enableAutoUpdate )
	 * 
	 * 
	 * @author aldo.bucchi@gmail.com
	 * 
	 */	
	public class AutoUpdateFlag
	{
		
		
		public static const FRAMES:String = "frames";
		
		
		public static const TIME:String = "time";
		
		
		
		
		/**
		 * 
		 * A dictionary with strong keys to hold timers while they are running.
		 * This ensures that they don't get collected by the GC
		 * 
		 */		
		private var activeTimers:Dictionary = new Dictionary( );
		
		
		
		private var counter:int = 0;


		private var updateFunction:Function;
		
		
		private var enableChangedHandler:Function;
		


		private var _hasPendingUpdate:Boolean;
		

		/**
		 * 
		 * 
		 * 
		 * @return 
		 * 
		 */
		public function get hasPendingUpdate( ):Boolean
		{
			return _hasPendingUpdate;		
		}
		
		


		/**
		 * 
		 * 
		 * 
		 * 
		 * @return 
		 * 
		 */
		public function get isEnabled( ):Boolean
		{
			return counter == 0;
		}




		private var _flags:Dictionary = new Dictionary( );
		/**
		 * 
		 * You can store flags ( changed property names, removed objects, etc )
		 * in this data structure so you can analize it in when the update is performed
		 * 
		 * you usually use it in the following way when using this class to help you with property updates
		 * 
		 * <pre>
		 * 
		 * public function set myVar( v:String ):void
		 * {
		 *     _myVar = v;
		 *     autoUpdateFlag.flags["myVar"] = true; // you leave a hint before calling the update
		 *     autoUpdateFlag.requestUpdate( );
		 * }
		 * 
		 * // later in your update function
		 * 
		 * private function update( ):void
		 * {
		 * 
		 *     // instead of just updating everything we use the hints
		 *     // to optimize 
		 * 
		 *     if ( autoUpdateFlag.flags["myVar"] == true )
		 *     {
		 *         trace( "we know ye changed, ye olde myVar" );
		 *     }
		 * 
		 * }
		 * 
		 * 
		 * </pre>
		 * 
		 * all entries in the dictionary are deleted after each update
		 * 
		 * The property is named "flags" instead of something more specific ( like "changedProperties" )
		 * because this class is very generic, it can be used for a lot of different stuff.
		 * 
		 * The semantics of its usage are entirely up to you
		 * 
		 * @return 
		 * 
		 */
		public function get flags( ):Dictionary
		{
			return _flags;
		}





		
		public function AutoUpdateFlag( updateFunction:Function = null, enableChangedHandler:Function = null )
		{
		
			
			this.updateFunction = updateFunction;
			
			
			this.enableChangedHandler = enableChangedHandler;
			
		
		}
		
		
		
		
		
		
		
		/**
		 * 
		 * 
		 * 
		 */		
		public function enable( ):void
		{
			
			if ( counter == 0 )
				return;
			
			counter --;
			
			
			if ( counter == 0 )
			{
				if ( enableChangedHandler != null )
				{
					enableChangedHandler.call( null, true );
				}
			}			
			
			
			
			if ( counter == 0 && _hasPendingUpdate )
			{
				doUpdate( );
			}
		
		}
		
		
		
		
		/**
		 * 
		 * Auto enable delay allows you to deferr enabling using a frame or time-based timer
		 * 
		 * @param autoEnableDelay
		 * @param autoEnableStrategy
		 * 
		 */		
		public function disable( autoEnableDelay:int = -1, autoEnableStrategy:String = FRAMES ):void
		{

			
			counter ++;


			if ( autoEnableDelay != -1 )
			{
			
				var timer:Timer;
				if ( autoEnableStrategy == TIME )
					timer = new Timer( autoEnableDelay, 1 );
				else
					timer = new FrameTimer( autoEnableDelay, 1 );
				
				
				timer.addEventListener( TimerEvent.TIMER_COMPLETE, onTimerComplete ); 
				timer.start( );
			
				activeTimers[ timer ] = true;
			
			
			}
			
			
			if ( counter == 1 )
			{
				if ( enableChangedHandler != null)
				{
					enableChangedHandler.call( null, false );
				}
			}
			
			
		
		}
		
		
		
		
		private function doUpdate( ):void
		{
			
			if ( updateFunction != null )
			{
				updateFunction.call( );
			}
			
		
			_hasPendingUpdate = false;
		
			
			// reset flags dictionary
			for ( var e:* in _flags )
			{
				delete _flags[ e ];
			}
			
		}
		
		

		/**
		 * 
		 * Will perform an update() if we are enabled, otherwise
		 * it will set the pending update to true.
		 * The next time we reached an enabled state, the update will be performed
		 * 
		 */		
		public function requestUpdate( ):void
		{

			if ( isEnabled )
				doUpdate( );		
			else
				_hasPendingUpdate = true;

		}
		
		

		/**
		 * 
		 * Will perform an update even if we are disabled
		 * 
		 */		
		public function forceUpdate( ):void
		{
		
			doUpdate( );

		}
		
		
		
		
		
		
		
		
		
		
		private function onTimerComplete( event:TimerEvent ):void
		{
			
			enable( );
			
			delete activeTimers[ event.target ];	
		
		}
	
		
	}
	
}